﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace DevFx.FrameWork.Linq
{
    public static class PredicateBuilder
    {
        public static Expression<Func<T, bool>> True<T>() { return f => true; }
        public static Expression<Func<T, bool>> False<T>() { return f => false; }

        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
                                                  Expression<Func<T, bool>> expr2)
        {
            var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
            return Expression.Lambda<Func<T, bool>>
                 (Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
        }

        public static Expression<Func<T, bool>> OrPredicate<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
        {
            //var invokedExpression = Expression.Invoke(expression2, expression1.Parameters.Cast<Expression>());
            //return Expression.Lambda<Func<T, bool>>(Expression.Or(expression1.Body, invokedExpression), expression1.Parameters);
            if (expression1 == null)
            {
                expression1 = expression2;
            }
            else
            {
                var map = expression1.Parameters.Select((f, i) => new { f, s = expression2.Parameters[i] })
                         .ToDictionary(p => p.s, p => p.f);

                var secondBody = ParameterRebinder.ReplaceParameters(map, expression2.Body);

                var binaryExpression = Expression.OrElse(expression1.Body, secondBody);
                expression1 = Expression.Lambda<Func<T, bool>>(binaryExpression, expression1.Parameters.Single());
            }
            return expression1;
        }

        public static Expression<Func<T, bool>> AndPredicate<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
        {
            if (expression1 == null)
            {
                expression1 = expression2;
            }
            else
            {
                var map = expression1.Parameters.Select((f, i) => new { f, s = expression2.Parameters[i] })
                         .ToDictionary(p => p.s, p => p.f);
                var secondBody = ParameterRebinder.ReplaceParameters(map, expression2.Body);

                var binaryExpression = Expression.AndAlso(expression1.Body, secondBody);
                expression1 = Expression.Lambda<Func<T, bool>>(binaryExpression, expression1.Parameters.Single());
            }
            return expression1;
        }

    }
    public class ParameterRebinder : ExpressionVisitor
    {
        private readonly Dictionary<ParameterExpression, ParameterExpression> map;

        public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
        {
            this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
        }

        public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
        {
            return new ParameterRebinder(map).Visit(exp);
        }

        protected override Expression VisitParameter(ParameterExpression p)
        {
            ParameterExpression replacement;
            if (map.TryGetValue(p, out replacement))
            {
                p = replacement;
            }
            return base.VisitParameter(p);
        }
    }
}
